#ifndef __M_SERVER_H__
#define __M_SERVER_H__

#pragma once

#include "util.hpp"
#include "db.hpp"
#include "online.hpp"
#include "room.hpp"
#include "session.hpp"
#include "matcher.hpp"

#define WEBROOT "./wwwroot"

class gobang_server
{
public:
    gobang_server(const std::string& host, const std::string& user, const std::string& pwd, const std::string& db, uint16_t port = 3306, 
                  const std::string& webroot = WEBROOT)
    : _ut(host, user, pwd, db, port),
      _om(),
      _rm(&_ut, &_om),
      _sm(&_wssvr),
      _mc(&_ut, &_rm, &_om),
      _webroot(webroot)
    {
        _wssvr.set_access_channels(websocketpp::log::alevel::none);
        _wssvr.init_asio();
        _wssvr.set_reuse_addr(true);

        _wssvr.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));//这里std::bind的作用是匹配函数类型 传入固定参数
        _wssvr.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));
        _wssvr.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));
        _wssvr.set_message_handler(std::bind(&gobang_server::wsmsg_callback, this, std::placeholders::_1, std::placeholders::_2));
    }

    void start(int port)
    {
        _wssvr.listen(port);
        _wssvr.start_accept();
        _wssvr.run();
    }

private:
    void handle_file_req(wsserver_t::connection_ptr& conn)
    {
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        std::string pathname = _webroot + uri;

        if (pathname.back() == '/')//浏览器输入框中没有请求任何资源 默认返回登录界面
            pathname += "login.html";

        std::string body;
        if (!file_util::read(pathname, body))
        {
            body = "<html><head><meta charset='UTF-8'/></head><body><h1>404 NOT FOUND</h1></body></html>";
            conn->set_status(websocketpp::http::status_code::not_found);
            conn->set_body(body);
            return;
        }
        //content-type content-length 如果不填充 http会自动填充对应的值
        conn->set_status(websocketpp::http::status_code::ok);
        conn->set_body(body);
    }

    void handle_reg_req(wsserver_t::connection_ptr& conn)//注册请求处理
    {
        //获取请求正文
        std::string req_body = conn->get_request_body();
        //对请求正文进行反序列化 得到用户名和密码
        Json::Value reg_info;
        //根据用户输入的用户名密码形成对应的响应
        Json::Value res;
        if (!json_util::deserialize(req_body, reg_info))//注册信息反序列化失败
        {
            LOG(ERR, "反序列化注册信息失败");
            http_response(false, "请求正文格式错误", websocketpp::http::status_code::bad_request, res, conn);
        }
        else if (reg_info["username"].isNull() || reg_info["password"].isNull())//用户未输入用户名/密码
        {
            LOG(ERR, "用户名密码不完整");
            http_response(false, "请输入用户名/密码", websocketpp::http::status_code::bad_request, res, conn);
        }
        else if (!_ut.insert(reg_info))//用户信息添加到数据库表失败 用户名已存在
        {
            LOG(ERR, "向数据库插入数据失败");
            http_response(false, "用户名已被占用", websocketpp::http::status_code::bad_request, res, conn);
        }
        else//用户信息添加到数据库表成功
        {
            LOG(DEG, "用户注册成功");
            http_response(true, "用户注册成功", websocketpp::http::status_code::ok, res, conn);
        }
    }

    void handle_login_req(wsserver_t::connection_ptr& conn)//登录请求处理
    {
        //获取请求正文
        std::string req_body = conn->get_request_body();
        //对请求正文进行反序列化 得到用户名和密码
        Json::Value login_info;
        //根据用户输入的用户名密码形成对应的响应
        Json::Value res;
        if (!json_util::deserialize(req_body, login_info))//登录信息反序列化失败
        {
            LOG(ERR, "反序列化登录信息失败");
            http_response(false, "请求正文格式错误", websocketpp::http::status_code::bad_request, res, conn);
        }
        else if (login_info["username"].isNull() || login_info["password"].isNull())//用户未输入用户名/密码
        {
            LOG(ERR, "用户名密码不完整");
            http_response(false, "请输入用户名/密码", websocketpp::http::status_code::bad_request, res, conn);
        }
        else if (!_ut.login(login_info))//用户名/密码错误
        {
            LOG(ERR, "验证数据库数据失败");
            http_response(false, "用户名/密码错误", websocketpp::http::status_code::bad_request, res, conn);
        }
        else//上面数据库验证成功会将用户详细信息返回到login_info里
        {
            uint64_t uid = login_info["id"].asInt64();
            if (_om.is_in_hall(uid) || _om.is_in_room(uid))
            {
                LOG(ERR, "用户重复登录");
                http_response(false, "用户重复登录", websocketpp::http::status_code::bad_request, res, conn);
                return;
            }
            session_ptr sp = _sm.create_session(uid, LOGIN);
            if (sp.get() == nullptr)
            {
                LOG(ERR, "创建session失败");
                http_response(false, "创建会话失败", websocketpp::http::status_code::internal_server_error, res, conn);
            }
            else
            {
                _sm.set_expire_time(sp->get_sid(), SESSION_TIMEOUT);//设置session过期时间
                std::string cookie_sid = "SSID=" + std::to_string(sp->get_sid());
                conn->append_header("Set-Cookie", cookie_sid);
                http_response(true, "登录成功", websocketpp::http::status_code::ok, res, conn);
            }
        }
    }

    void handle_info_req(wsserver_t::connection_ptr& conn)//用户信息请求处理
    {
        //获取请求中的cookie信息
        std::string cookie_str = conn->get_request_header("cookie");
        
        Json::Value res;

        if (cookie_str.empty())//没有cookie信息
        {
            LOG(ERR, "请求中没有cookie信息");
            http_response(false, "没有cookie信息 请重新登录", websocketpp::http::status_code::bad_request, res, conn);
            return;
        }

        std::string sid;
        if (!get_cookie_val(cookie_str, "SSID", sid))//cookie信息中没有session id
        {
            LOG(ERR, "cookie中没有session id");
            http_response(false, "cookie中没有session id 请重新登录", websocketpp::http::status_code::bad_request, res, conn);
            return;
        }

        session_ptr sp = _sm.get_session(std::stol(sid));
        if (sp.get() == nullptr)//通过session id没有找到session信息
        {
            LOG(ERR, "没有找到session信息");
            http_response(false, "没有找到session信息 请重新登录", websocketpp::http::status_code::bad_request, res, conn);
            return;
        }

        uint64_t uid = sp->get_user();
        if (!_ut.select_by_id(uid, res))//通过session信息中的用户id没有找到对应的用户信息
        {
            LOG(ERR, "没有找到用户信息");
            http_response(false, "没有找到用户信息 请重新登录", websocketpp::http::status_code::bad_request, res, conn);
            return;
        }
        http_response(true, "用户信息获取成功", websocketpp::http::status_code::ok, res, conn);
        _sm.set_expire_time(std::stol(sid), SESSION_TIMEOUT);//重置session过期时间
    }

    void http_response(bool result, const std::string& reason, websocketpp::http::status_code::value code, 
                       Json::Value& res, wsserver_t::connection_ptr& conn)
    {
        res["result"] = result;
        res["reason"] = reason;

        std::string res_body;
        json_util::serialize(res, res_body);

        conn->set_status(code);
        conn->append_header("Content-Type", "application/json");
        conn->set_body(res_body);
    }

    bool get_cookie_val(const std::string& cookie_str, const std::string& key, std::string& val)
    {   
        std::vector<std::string> ret;
        string_util::split(cookie_str, "; ", ret);
        for (auto& s : ret)
        {
            std::vector<std::string> kv;
            string_util::split(s, "=", kv);
            if (kv.size() != 2)
                continue;
            if (kv[0] == key)
            {
                val = kv[1];
                return true;
            }
        }
        return false;
    }

    void http_callback(websocketpp::connection_hdl hdl)
    {
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        //动态数据请求
        if (method == "POST" && uri == "/reg")//注册请求
            handle_reg_req(conn);
        else if (method == "POST" && uri == "/login")//登录请求
            handle_login_req(conn);
        else if (method == "GET" && uri == "/info")//个人信息请求
            handle_info_req(conn);
        else//静态页面请求
            handle_file_req(conn);
    }

    

    void wsopen_callback(websocketpp::connection_hdl hdl)//websocket长连接建立成功之后的处理函数
    {
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        if (req.get_uri() == "/hall")
        {
            wsopen_game_hall(conn);
        }
        else if (req.get_uri() == "/room")
        {
            wsopen_game_room(conn);
        }
    }

    void wsclose_callback(websocketpp::connection_hdl hdl)//websocket长连接关闭的处理函数
    {
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        if (req.get_uri() == "/hall")
        {
            wsclose_game_hall(conn);
        }
        else if (req.get_uri() == "/room")
        {
            wsclose_game_room(conn);
        }
    }

    void wsmsg_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg)//websocket连接消息处理函数
    {
        wsserver_t::connection_ptr conn = _wssvr.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        if (req.get_uri() == "/hall")
        {
            wsmsg_game_hall(conn, msg);
        }
        else if (req.get_uri() == "/room")
        {
            wsmsg_game_room(conn, msg);
        }
    }

    void wsopen_game_hall(wsserver_t::connection_ptr& conn)//游戏大厅长连接建立成功的处理函数
    {
        //获取当前客户端session信息
        std::string cookie_str = conn->get_request_header("cookie");//获取请求中的cookie信息
        
        Json::Value res;

        if (cookie_str.empty())//没有cookie信息
        {
            LOG(ERR, "请求中没有cookie信息");
            res["optype"] = "enter_hall";
            res["result"] = false;
            res["reason"] = "没有cookie信息 请重新登录";
            ws_response(conn, res);
            return;
        }

        std::string sid;
        if (!get_cookie_val(cookie_str, "SSID", sid))//cookie信息中没有session id
        {
            LOG(ERR, "cookie中没有session id");
            res["optype"] = "enter_hall";
            res["result"] = false;
            res["reason"] = "cookie中没有session id 请重新登录";
            ws_response(conn, res);
            return;
        }

        session_ptr sp = _sm.get_session(std::stol(sid));
        if (sp.get() == nullptr)//通过session id没有找到session信息
        {
            LOG(ERR, "没有找到session信息");
            res["optype"] = "enter_hall";
            res["result"] = false;
            res["reason"] = "没有找到session信息 请重新登录";
            ws_response(conn, res);
            return;
        }

        //返回建立大厅长连接成功的响应
        uint64_t uid = sp->get_user();
        _om.enter_hall(uid, conn);//将当前客户端及其连接添加到游戏大厅中
        res["optype"] = "enter_hall";
        res["result"] = true;
        res["reason"] = "进入游戏大厅成功";
        ws_response(conn, res);
        _sm.set_expire_time(sp->get_sid(), SESSION_FOREVER);//将session设为永久存在
    }

    void wsmsg_game_hall(wsserver_t::connection_ptr& conn, wsserver_t::message_ptr msg)
    {
        //通过连接中的cookie信息获取用户session信息
        std::string cookie_str = conn->get_request_header("cookie");//获取请求中的cookie信息
        
        Json::Value res;

        if (cookie_str.empty())//没有cookie信息
        {
            LOG(ERR, "请求中没有cookie信息");
            res["result"] = false;
            res["reason"] = "没有cookie信息 请重新登录";
            ws_response(conn, res);
            return;
        }

        std::string sid;
        if (!get_cookie_val(cookie_str, "SSID", sid))//cookie信息中没有session id
        {
            LOG(ERR, "cookie中没有session id");
            res["result"] = false;
            res["reason"] = "cookie中没有session id 请重新登录";
            ws_response(conn, res);
            return;
        }

        session_ptr sp = _sm.get_session(std::stol(sid));
        if (sp.get() == nullptr)//通过session id没有找到session信息
        {
            LOG(ERR, "没有找到session信息");
            res["result"] = false;
            res["reason"] = "没有找到session信息 请重新登录";
            ws_response(conn, res);
            return;
        }

        //获取客户端消息请求内容 将用户添加到匹配队列/从匹配队列中删除 并返回对应的响应
        std::string req_body = msg->get_payload();
        Json::Value req;
        if (!json_util::deserialize(req_body, req))
        {
            res["result"] = false;
            res["reason"] = "请求解析失败";
            ws_response(conn, res);
            return;
        }

        if (!req["optype"].isNull() && req["optype"].asString() == "match_start")
        {
            res["optype"] = "match_start";
            res["result"] = true;
            res["reason"] = "开始对战匹配";
            ws_response(conn, res);
            //先发送开始匹配消息再添加到队列 因为添加到队列之后可能立即匹配成功并发送匹配成功消息 有可能开始匹配消息比匹配成功消息还晚一点发送

            //将用户添加到匹配队列
            if (_mc.add(sp->get_user()))
                LOG(DEG, "user:%lu add to match_queue success", sp->get_user());
            else
                LOG(DEG, "user:%lu add to match_queue failed", sp->get_user());
        }
        else if (!req["optype"].isNull() && req["optype"].asString() == "match_stop")
        {
            res["optype"] = "match_stop";
            res["result"] = true;
            res["reason"] = "停止对战匹配";
            ws_response(conn, res);

            //将用户从匹配队列中删除
            if (_mc.del(sp->get_user()))
                LOG(DEG, "user:%lu del from match_queue success", sp->get_user());
            else
                LOG(DEG, "user:%lu del from match_queue failed", sp->get_user());
        }
        else
        {
            res["result"] = false;
            res["reason"] = "unknown optype";
            ws_response(conn, res);
        }
    }

    void wsclose_game_hall(wsserver_t::connection_ptr& conn)//游戏大厅长连接关闭的处理函数
    {
        //通过连接中的cookie信息获取session信息
        std::string cookie_str = conn->get_request_header("cookie");//获取请求中的cookie信息
        
        if (cookie_str.empty())//没有cookie信息
        {
            LOG(ERR, "请求中没有cookie信息");
            return;
        }

        std::string sid;
        if (!get_cookie_val(cookie_str, "SSID", sid))//cookie信息中没有session id
        {
            LOG(ERR, "cookie中没有session id");
            return;
        }   

        session_ptr sp = _sm.get_session(std::stol(sid));
        if (sp.get() == nullptr)//通过session id没有找到session信息
        {
            LOG(ERR, "没有找到session信息");
            return;
        }

        uint64_t uid = sp->get_user();
        //将用户从游戏大厅中移除
        _om.exit_hall(uid);
        LOG(DEG, "用户离开游戏大厅");
        //为session设置定时删除
        _sm.set_expire_time(sp->get_sid(), SESSION_TIMEOUT);
    }

    void wsopen_game_room(wsserver_t::connection_ptr& conn)//游戏房间长连接建立成功的处理函数
    {
        //获取当前客户端session信息
        std::string cookie_str = conn->get_request_header("cookie");//获取请求中的cookie信息
        
        Json::Value res;

        if (cookie_str.empty())//没有cookie信息
        {
            LOG(ERR, "请求中没有cookie信息");
            res["optype"] = "enter_room";
            res["result"] = false;
            res["reason"] = "没有cookie信息 请重新登录";
            ws_response(conn, res);
            return;
        }

        std::string sid;
        if (!get_cookie_val(cookie_str, "SSID", sid))//cookie信息中没有session id
        {
            LOG(ERR, "cookie中没有session id");
            res["optype"] = "enter_room";
            res["result"] = false;
            res["reason"] = "cookie中没有session id 请重新登录";
            ws_response(conn, res);
            return;
        }

        session_ptr sp = _sm.get_session(std::stol(sid));
        if (sp.get() == nullptr)//通过session id没有找到session信息
        {
            LOG(ERR, "没有找到session信息");
            res["optype"] = "enter_room";
            res["result"] = false;
            res["reason"] = "没有找到session信息 请重新登录";
            ws_response(conn, res);
            return;
        }

        //判断当前用户是否在游戏大厅或其他游戏房间中
        uint64_t uid = sp->get_user();
        if (_om.is_in_hall(uid) || _om.is_in_room(uid))
        {
            LOG(ERR, "用户重复登录");
            res["optype"] = "enter_room";
            res["result"] = false;
            res["reason"] = "用户重复登录";
            ws_response(conn, res);
            return;
        }
        
        //判断当前用户是否已有房间
        room_ptr rp = _rm.get_room_by_uid(uid);
        if (rp.get() == nullptr)
        {
            LOG(ERR, "未找到用户房间信息");
            res["optype"] = "enter_room";
            res["result"] = false;
            res["reason"] = "未找到用户房间信息";
            ws_response(conn, res);
            return;
        }

        //将当前用户添加到在线游戏房间管理模块中
        _om.enter_room(uid, conn);

        //将session设为永久存在
        _sm.set_expire_time(sp->get_sid(), SESSION_FOREVER);

        //向客户端返回进入房间成功的响应
        res["optype"] = "enter_room";
        res["result"] = true;
        res["reason"] = "进入游戏房间成功";
        res["room_id"] = (Json::UInt64)rp->get_room_id();
        res["uid"] = (Json::UInt64)uid;
        res["white_id"] = (Json::UInt64)rp->get_white_id();
        res["black_id"] = (Json::UInt64)rp->get_black_id();
        ws_response(conn, res);
    }

    void wsmsg_game_room(wsserver_t::connection_ptr& conn, wsserver_t::message_ptr msg)
    {
        //获取客户端session信息
        std::string cookie_str = conn->get_request_header("cookie");//获取请求中的cookie信息
        
        Json::Value res;

        if (cookie_str.empty())//没有cookie信息
        {
            LOG(ERR, "请求中没有cookie信息");
            res["optype"] = "unknown";
            res["result"] = false;
            res["reason"] = "没有cookie信息 请重新登录";
            ws_response(conn, res);
            return;
        }

        std::string sid;
        if (!get_cookie_val(cookie_str, "SSID", sid))//cookie信息中没有session id
        {
            LOG(ERR, "cookie中没有session id");
            res["optype"] = "unknown";
            res["result"] = false;
            res["reason"] = "cookie中没有session id 请重新登录";
            ws_response(conn, res);
            return;
        }

        session_ptr sp = _sm.get_session(std::stol(sid));
        if (sp.get() == nullptr)//通过session id没有找到session信息
        {
            LOG(ERR, "没有找到session信息");
            res["optype"] = "unknown";
            res["result"] = false;
            res["reason"] = "没有找到session信息 请重新登录";
            ws_response(conn, res);
            return;
        }

        //获取房间信息
        room_ptr rp = _rm.get_room_by_uid(sp->get_user());
        if (rp.get() == nullptr)
        {
            LOG(ERR, "未找到用户房间信息");
            res["optype"] = "unknown";
            res["result"] = false;
            res["reason"] = "未找到用户房间信息";
            ws_response(conn, res);
            return;
        }

        //将消息请求反序列化
        std::string req_body = msg->get_payload();
        Json::Value req;
        if (!json_util::deserialize(req_body, req))
        {
            LOG(ERR, "请求解析失败");
            res["optype"] = "unknown";
            res["result"] = false;
            res["reason"] = "请求解析失败";
            ws_response(conn, res);
            return;
        }

        //处理消息请求
        rp->handle_request(req);
    }

    void wsclose_game_room(wsserver_t::connection_ptr& conn)//游戏房间长连接关闭的处理函数
    {
        //通过连接中的cookie信息获取session信息
        std::string cookie_str = conn->get_request_header("cookie");//获取请求中的cookie信息
        
        if (cookie_str.empty())//没有cookie信息
        {
            LOG(ERR, "请求中没有cookie信息");
            return;
        }

        std::string sid;
        if (!get_cookie_val(cookie_str, "SSID", sid))//cookie信息中没有session id
        {
            LOG(ERR, "cookie中没有session id");
            return;
        }   

        session_ptr sp = _sm.get_session(std::stol(sid));
        if (sp.get() == nullptr)//通过session id没有找到session信息
        {
            LOG(ERR, "没有找到session信息");
            return;
        }

        //将用户从在线房间管理模块中移除
        uint64_t uid = sp->get_user();
        _om.exit_room(uid);

        //将用户从游戏房间模块中移除 游戏中退出对方胜利 双方都退出房间则房间关闭
        _rm.remove_user_from_room(uid);
        LOG(DEG, "用户离开游戏房间");

        //设置session过期时间
        _sm.set_expire_time(sp->get_sid(), SESSION_TIMEOUT);
    }

    void ws_response(wsserver_t::connection_ptr& conn, Json::Value& res)
    {
        std::string res_body;
        json_util::serialize(res, res_body);
        conn->send(res_body);
    }

private:
    user_table _ut;
    online_manager _om;
    room_manager _rm;
    session_manager _sm;
    matcher<uint64_t> _mc;
    wsserver_t _wssvr;
    std::string _webroot;//静态资源根目录 ./register.html -> ./wwwroot/register.html
};

#endif